package org.qpcr.community.auth.service;

import org.apache.commons.codec.digest.DigestUtils;
import org.hswebframework.ezorm.rdb.mapping.ReactiveQuery;
import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
import org.hswebframework.ezorm.rdb.mapping.defaults.SaveResult;
import org.hswebframework.web.api.crud.entity.PagerResult;
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.ReactiveAuthenticationManager;
import org.hswebframework.web.bean.FastBeanCopier;
import org.hswebframework.web.crud.query.QueryHelper;
import org.hswebframework.web.crud.service.GenericReactiveCrudService;
import org.hswebframework.web.i18n.LocaleUtils;
import org.hswebframework.web.system.authorization.api.entity.UserEntity;
import org.hswebframework.web.system.authorization.api.event.UserDeletedEvent;
import org.hswebframework.web.system.authorization.api.service.reactive.ReactiveUserService;
import org.hswebframework.web.validator.ValidatorUtils;
import org.qpcr.community.auth.entity.UserDetail;
import org.qpcr.community.auth.entity.UserDetailEntity;
import org.qpcr.community.auth.enums.DefaultUserEntityType;
import org.qpcr.community.auth.enums.UserEntityTypes;
import org.qpcr.community.auth.service.request.SaveUserDetailRequest;
import org.qpcr.community.auth.service.request.SaveUserRequest;
import org.qpcr.community.auth.web.excel.ImportResult;
import org.qpcr.community.auth.web.excel.UserExcelImporter;
import org.qpcr.community.auth.web.excel.UserExcelInfo;
import org.qpcr.community.io.excel.AbstractImporter;
import org.qpcr.community.io.file.FileManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.reactive.TransactionalOperator;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.concurrent.Queues;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 用户详情管理
 * <p>
 * 通过通用增删改查接口实现用户详情增删改查功能.
 * 通过用户id获取用户基本信息（包含租户成员信息）
 *
 * @author zhouhao
 * @see org.hswebframework.web.crud.service.GenericReactiveCacheSupportCrudService
 * @since 1.3
 */
@Service
public class UserDetailService extends GenericReactiveCrudService<UserDetailEntity, String> {

    private final ReactiveUserService userService;

    @Autowired
    private ReactiveRepository<UserDetailEntity, String> repository;

    private final RoleService roleService;
    private final OrganizationService organizationService;

    private final ReactiveAuthenticationManager authenticationManager;

    private final static UserDetailEntity emptyDetail = new UserDetailEntity();
    private final QueryHelper queryHelper;
    private final FileManager fileManager;
    private final WebClient webClient;
    private final TransactionalOperator transactionalOperator;

    public UserDetailService(ReactiveUserService userService,
                             RoleService roleService,
                             OrganizationService organizationService,
                             ReactiveAuthenticationManager authenticationManager,
                             QueryHelper queryHelper,
                             FileManager fileManager,
                             WebClient.Builder builder,
                             TransactionalOperator transactionalOperator) {
        this.userService = userService;
        this.roleService = roleService;
        this.organizationService = organizationService;
        this.authenticationManager = authenticationManager;
        this.queryHelper = queryHelper;
        this.fileManager = fileManager;
        this.webClient = builder.build();
        this.transactionalOperator = transactionalOperator;
        // 注册默认用户类型
        UserEntityTypes.register(Arrays.asList(DefaultUserEntityType.values()));

    }

    /**
     * 根据用户id获取用户详情
     *
     * @param userId 用户id
     * @return 详情信息
     */
    public Mono<UserDetail> findUserDetail(String userId) {
        return Mono
            .zip(
                userService.findById(userId), // 基本信息
                this.findById(userId).defaultIfEmpty(emptyDetail), // 详情
//                memberService.findMemberDetail(userId).collectList(), // 租户成员信息
                authenticationManager       //用户维度信息
                                            .getByUserId(userId)
                                            .map(Authentication::getDimensions)
                                            .defaultIfEmpty(Collections.emptyList())
            )
            .map(tp4 -> UserDetail
                     .of(tp4.getT1())
                     .with(tp4.getT2())
//                .with(tp4.getT3())
                     .withDimension(tp4.getT3())
            );
    }

    /**
     * 根据用户id和用户信息保存用户详情
     *
     * @param userId  用户ID
     * @param request 详情信息
     * @return void
     */
    public Mono<Void> saveUserDetail(String userId, SaveUserDetailRequest request) {
        ValidatorUtils.tryValidate(request);
        UserDetailEntity entity = FastBeanCopier.copy(request, new UserDetailEntity());
        entity.setId(userId);

        UserEntity userEntity = new UserEntity();
        userEntity.setId(userId);
        userEntity.setName(request.getName());

        return this
            .save(entity)
            .then(userService.saveUser(Mono.just(userEntity)))
            .as(LocaleUtils::transform)
            .then();
    }




    public Mono<PagerResult<UserDetail>> queryUserDetail(QueryParamEntity query) {
        return Mono
            .zip(
                userService.countUser(query),
                userService.findUser(query).collectList())
            .flatMap(tp2 -> {
                List<UserEntity> userList = tp2.getT2();
                return this.createQuery()
                           .in(UserDetailEntity::getId, userList
                               .stream()
                               .map(UserEntity::getId)
                               .collect(Collectors.toList()))
                           .fetch()
                           .collectMap(UserDetailEntity::getId)
                           .flatMap(userDetailMap -> {
                               List<UserDetail> userDetailList = userList.stream()
                                                                         .map(user -> {
                                                                             UserDetail userDetail = UserDetail.of(user);
                                                                             UserDetailEntity entity = userDetailMap.get(user.getId());
                                                                             if (entity != null) {
                                                                                 userDetail = userDetail.with(entity);
                                                                             }
                                                                             return userDetail;
                                                                         })
                                                                         .collect(Collectors.toList());
                               return Mono.just(PagerResult.of(tp2.getT1(), userDetailList, query));
                           });
            });
    }

    /**
     * 保存用户,自动关联角色{@link SaveUserRequest#getRoleIdList()}以及机构(部门){@link SaveUserRequest#getOrgIdList()}
     *
     * @param request 保存请求
     * @return 用户ID
     */
    @Transactional
    public Mono<String> saveUser(SaveUserRequest request) {
        request.validate();
        UserDetail detail = request.getUser();
        boolean isUpdate = StringUtils.hasText(detail.getId());
        UserEntity entity = request.getUser().toUserEntity();
        return userService
            .saveUser(Mono.just(entity))//保存s_user账号表
            .then(Mono.fromSupplier(entity::getId))
            .flatMap(userId -> {
                String uUnitId=new ArrayList<>(request.getOrgIdList()).get(0);
                detail.setId(userId);
                detail.setUnitId(uUnitId);
                detail.setOrgId(uUnitId);
                //保存详情
                return this
                    .save(detail.toDetailEntity())//保存s_user_detail扩展表信息
                    //绑定角色
                    .then(roleService.bindUser(Collections.singleton(userId), request.getRoleIdList(), isUpdate))
                    //绑定机构部门
                    .then(organizationService.bindUser(Collections.singleton(userId), request.getOrgIdList(), isUpdate))
                    .thenReturn(userId);
            })
            .as(LocaleUtils::transform);
    }

    /**
     * 删除用户时同时删除用户详情
     *
     * @param event 用户删除事件
     */
    @EventListener
    public void handleUserDeletedEvent(UserDeletedEvent event) {

        event.async(
//            this.deleteById(event.getUser().getId());
            this.findById(event.getUser().getId())
                .flatMap(userDetail -> {
                    userDetail.setDeleteType(1);
                    userDetail.setModifyTimeNow();
                    return this.updateById(userDetail.getId(), userDetail);
                })
        );
    }

    /**
     * 更新用户二维码信息
     *
     */
    public Mono<Integer>  updateUserDetailCode(UserDetailEntity userDetail) {

        return this.createUpdate()
                   .set(UserDetailEntity::getCodeId, userDetail.getCodeId())
                   .set(UserDetailEntity::getCode, userDetail.getCode())
                   .in(UserDetailEntity::getId, userDetail.getId())
                   .execute();

    }


    /**
     * 更新用户信息(修改密码后更新修改时间)
     */
    public Mono<Boolean>  updateUserUpTime(UserDetailEntity userDetail) {

        return this.createUpdate()
                   .set(UserDetailEntity::getName, userDetail.getName())
                   .in(UserDetailEntity::getId, userDetail.getId()).execute().thenReturn(true);

    }

    /**
     * 验证二维码信息
     *
     */
    public Mono<Integer>  validateCode(String code,String id) {
        if(id!=null){
            return this.createQuery().where(UserDetailEntity::getCode,code).not(UserDetailEntity::getId,id).fetch().collectList().map(userDetailEntities ->{
                return userDetailEntities.size();
            });
        }else{
            return this.createQuery().where(UserDetailEntity::getCode,code).fetch().collectList().map(userDetailEntities ->{
                return userDetailEntities.size();
            });
        }


    }


    public Mono<UserDetailEntity> findByUserPhone(String phone,String id) {
        return Mono.justOrEmpty(phone).flatMap((_name) -> {
            return ((ReactiveQuery)this.repository.createQuery().where(UserDetailEntity::getTelephone, _name).not(UserDetailEntity::getId, id)).fetchOne();
        });
    }
    public Mono<UserDetailEntity> findByUserPhone(String phone) {
        return Mono.justOrEmpty(phone).flatMap((_name) -> {
            return ((ReactiveQuery)this.repository.createQuery().where(UserDetailEntity::getTelephone, _name)).fetchOne();
        });
    }


    public static void main(String[] args) {
        System.out.println(String.format("hsweb.%s.framework.%s", "Hgy19880203", "salt"));
        System.out.println(DigestUtils.md5Hex(String.format("hsweb.%s.framework.%s", "hugy", "fpXDQ0wGZ3-zXkDCrlI7vv1qAZdfGzaj")));
    }

    /**
     * 导入用户数据
     *
     * @param fileUrl 附件地址
     * @param speed   speed
     * @return Flux<ImportResult>
     */
    public Flux<ImportResult> doBatchImportWithLog(String fileUrl, int speed) {
        return Authentication
                .currentReactive()
                .flatMapMany(auth -> Mono.zip(
                                        // 用户
                                        queryHelper.select(" select s.username from s_user s", UserDetail::new)
                                                .fetch().collectList(),
                                        // 组织
                                        organizationService.createQuery().fetch().collectList(),
                                        // 角色
                                        roleService.createQuery().fetch().collectList()
                                )
                                .map(tp -> new UserExcelImporter(fileManager, webClient, tp.getT1(), tp.getT2(), tp.getT3(), auth))
                                .flatMapMany(importer -> importer
                                        .doImport(fileUrl)
                                        .groupBy(
                                                result -> result.isSuccess() && result.getType() == AbstractImporter.ImportResultType.data,
                                                Integer.MAX_VALUE
                                        )
                                        .flatMap(group -> {
                                            // 处理导入成功的用户
                                            if (group.key()) {
                                                return group
                                                        .map(result -> result.getData())
                                                        .as(flux -> handleImportDevice(flux, speed));
                                            }
                                            // 返回错误信息和导入结果详情文件地址
                                            return group
                                                    .map(result -> {
                                                        ImportResult response = new ImportResult();
                                                        response.setSuccess(result.isSuccess());
                                                        if (StringUtils.hasText(result.getMessage())) {
                                                            response.setMessage(String.format("第%d行：%s", result.getRow(), result.getMessage()));
                                                        }
                                                        response.setDetailFile(result.getDetailFile());
                                                        return response;
                                                    });
                                        })
                                )
                );
    }

    private Flux<ImportResult> handleImportDevice(Flux<UserExcelInfo> flux,
                                                  int speed) {
        return flux
                .buffer(100)//每100条数据保存一次
                .map(Flux::fromIterable)
                .flatMap(buffer -> Mono.zip(
                                        buffer.flatMap(
                                                userExcelInfo -> {
                                                    if (Boolean.FALSE.equals(userExcelInfo.getOrgIsExist())) {
                                                        return organizationService.save(userExcelInfo.getOrganizationSaveEntity())
                                                                .thenReturn(this.saveUser(userExcelInfo.getSaveUserRequest()));
                                                    }else {
                                                        return this.saveUser(userExcelInfo.getSaveUserRequest());
                                                    }
                                                }
                                        ).collect(Collectors.toList()),
                                        Mono.just(SaveResult.of(0, 0))
                                )
                                .as(transactionalOperator::transactional),
                        Math.min(speed, Queues.XS_BUFFER_SIZE)
                )
                .map(res -> ImportResult.success(SaveResult.of(3, 0)))
                .onErrorResume(err -> Mono.just(ImportResult.error(err)));
    }

    /**
     * 根据id查询用户信息
     *
     * @param userId 用户id
     * @return Flux<UserDetail>
     */
    public Flux<UserDetail> getUserDetailById(String userId) {
        String sql = " select d.id," +
                "d.name," +
                "s.username," +
                "s.password," +
                "s.salt," +
                "d.org_id as orgId," +
                "d.unit_id as unitId," +
                "d.telephone," +
                "d.role_type roleType," +
                "d.create_time createTime," +
                "d.modify_time modifyTime," +
                "d.delete_type deleteType " +
                " from s_user s left join s_user_detail d on d.id=s.id where s.id='" + userId + "'";

        return queryHelper.select(sql,UserDetail::new).fetch();
    }

    /**
     * 根据code查询用户信息
     *
     * @param userId 用户id
     * @return Flux<UserDetail>
     */
    public Flux<UserDetail> findUserByCode(String userId) {
        String sql = " select d.id," +
            "d.name," +
            "s.username," +
            "s.password," +
            "s.salt," +
            "d.org_id as orgId," +
            "d.unit_id as unitId," +
            "d.telephone," +
            "d.role_type roleType," +
            "d.create_time createTime," +
            "d.modify_time modifyTime," +
            "d.delete_type deleteType " +
            " from s_user s left join s_user_detail d on d.id=s.id where s.id='" + userId + "'";

        return queryHelper.select(sql,UserDetail::new).fetch();
    }


    public Mono<UserEntity> UserEntityfindById(String id){
        return userService.findById(id);
    };

    public Mono<UserEntity> findByUsernameAndPassword(String username, String plainPassword){
        return userService.findByUsernameAndPassword(username,plainPassword);
    };

    public Mono<Boolean> updateLastLoginTime(String userId) {
        return repository.createUpdate()
                  .set(UserDetailEntity::getLastLoginTime, LocalDateTime.now().toInstant(ZoneOffset.UTC).toEpochMilli())
                  .where(UserDetailEntity::getId, userId)
                  .execute()
                  .map(result -> result > 0);

    }
}
